/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.nf.health.app.service;

import android.annotation.SuppressLint;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

import com.nf.health.app.models.DeviceData;
import com.nf.health.app.utils.CRC8;
import com.nf.health.app.utils.DateUtil;
import com.nf.health.app.utils.LogUtil;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * Service for managing connection and data communication with a GATT server hosted on a
 * given Bluetooth LE device.
 */
@SuppressLint("NewApi")
public class BluetoothLeService extends Service {
    private final static String TAG = BluetoothLeService.class.getSimpleName();
    public static boolean isConnected = false;
    private BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private String mBluetoothDeviceAddress;
    private BluetoothGatt mBluetoothGatt;

    public final static String ACTION_GATT_CONNECTED           = "com.nf.health.app.service.ACTION_GATT_CONNECTED";
    public final static String ACTION_GATT_DISCONNECTED        = "com.nf.health.app.service.ACTION_GATT_DISCONNECTED";
    public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.nf.health.app.service.ACTION_GATT_SERVICES_DISCOVERED";


    private String type = "";//设备类型

    @Override
    public void onCreate() {
        super.onCreate();
    }

    public final static String ACTION_DATA_AVAILABLE           = "com.nf.health.app.service.ACTION_DATA_AVAILABLE";
    public final static String EXTRA_DATA                      = "com.nf.health.app.service.EXTRA_DATA";

    private Handler mHandler = new Handler() {};

    private int connectTimes = 0;
    // Implements callback methods for GATT events that the app cares about.  For example,
    // connection change and services discovered.
    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            LogUtil.i(TAG, "onConnectionStateChange status:"+status+">>>>newState:"+newState);
            gatt.discoverServices();
            if(newState == 0){
                isConnected = false;
                if(mBluetoothGatt != null){
                    try {
                        mBluetoothGatt.disconnect();
                        mBluetoothGatt.close();
                        mBluetoothGatt = null;
                    } catch (Exception e) {
                        boolean flag = BluetoothLeService.this.mBluetoothGatt.discoverServices();
                        LogUtil.i("BluetoothLeService", "开始to start service discovery:" + flag);
                    }
                    broadcastUpdate(ACTION_GATT_DISCONNECTED);
                }
            } else if(newState == 2 && status == 0){
                isConnected = true;
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if(BluetoothLeService.this.mBluetoothGatt != null) {
                            try {
                                boolean flag = BluetoothLeService.this.mBluetoothGatt.discoverServices();
                                Log.w("BluetoothLeService", "开始to start service discovery:" + flag);
                            } catch (Exception var2) {
                            }
                        }
                    }
                }, 600L);
                broadcastUpdate(ACTION_GATT_CONNECTED);
            }else if(status == 133 && newState == 2 && connectTimes < 4) {
                isConnected = false;
                Log.d("BluetoothLeService", " repeat connect.." +connectTimes);
//                BluetoothLeService.this.connectTimes = BluetoothLeService.this.connectTimes + 1;
//                BluetoothLeService.this.mHandler.postDelayed(new Runnable() {
//                    public void run() {
//                        BluetoothLeService.this.connect2(BluetoothLeService.mDeviceAddress);
//                    }
//                }, 3000L);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            LogUtil.i(TAG, "onServicesDiscovered status:"+status);
//            if (status == BluetoothGatt.GATT_SUCCESS) {
//                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
//            } else {
//                Log.w(TAG, "onServicesDiscovered received: " + status);
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    // TODO Auto-generated catch block
//                    e.printStackTrace();
//                }
//                mBluetoothGatt.discoverServices();
//            }+
            List<BluetoothGattService> services= gatt.getServices();
            if (status == BluetoothGatt.GATT_SUCCESS){
                try {
                    Thread.sleep(200L);
                } catch (InterruptedException var4) {
                    var4.printStackTrace();
                }
                if(DeviceData.Type.BLOOD_PRESSURE.equals(type)){
                    BluetoothGattService mBluetoothGattService = mBluetoothGatt.getService(UUID.fromString(SampleGattAttributes.BloodPressure.SERVICE_UUID));
                    BluetoothGattCharacteristic mBluetoothGattServiceCharacteristic = mBluetoothGattService.getCharacteristic(UUID.fromString(SampleGattAttributes.BloodPressure.NOTIFY_UUID));
                    setCharacteristicNotification(mBluetoothGattServiceCharacteristic, true);
                }else if(DeviceData.Type.BLOOD_SUGAR.equals(type)){
                    BluetoothGattService mBluetoothGattService = mBluetoothGatt.getService(UUID.fromString(SampleGattAttributes.BloodSugar.SERVICE_UUID));
                    BluetoothGattCharacteristic mBluetoothGattServiceCharacteristic = mBluetoothGattService.getCharacteristic(UUID.fromString(SampleGattAttributes.BloodSugar.NOTIFY_UUID));
                    setCharacteristicNotification(mBluetoothGattServiceCharacteristic, true);
                }else if(DeviceData.Type.URINE.equals(type)){
                    BluetoothGattService mBluetoothGattService = mBluetoothGatt.getService(UUID.fromString(SampleGattAttributes.Urine.SERVICE_UUID));
                    BluetoothGattCharacteristic mBluetoothGattServiceCharacteristic = mBluetoothGattService.getCharacteristic(UUID.fromString(SampleGattAttributes.Urine.NOTIFY_UUID));
                    setCharacteristicNotification(mBluetoothGattServiceCharacteristic, true);
                }else if(DeviceData.Type.SWEAT.equals(type)){
                    BluetoothGattService mBluetoothGattService = mBluetoothGatt.getService(UUID.fromString(SampleGattAttributes.Sweat.SERVICE_UUID));
                    BluetoothGattCharacteristic mBluetoothGattServiceCharacteristic = mBluetoothGattService.getCharacteristic(UUID.fromString(SampleGattAttributes.Sweat.NOTIFY_UUID));
                    setCharacteristicNotification(mBluetoothGattServiceCharacteristic, true);
                }else if(DeviceData.Type.OXYGEN.equals(type)){ //xu血痒
                    BluetoothGattService mBluetoothGattService = mBluetoothGatt.getService(UUID.fromString(SampleGattAttributes.BloodOxygen.SERVICE_UUID));
                    BluetoothGattCharacteristic mBluetoothGattServiceCharacteristic = mBluetoothGattService.getCharacteristic(UUID.fromString(SampleGattAttributes.BloodOxygen.WRITE_UUID));
                    setCharacteristicNotification(mBluetoothGattServiceCharacteristic, true);
                }
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            LogUtil.i(TAG, "onCharacteristicRead status:"+status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            LogUtil.i(TAG, "onDescriptorRead"+descriptor.getUuid() );
            super.onDescriptorRead(gatt, descriptor, status);
        }
    };

    private void broadcastUpdate(final String action) {
        final Intent intent = new Intent(action);
        sendBroadcast(intent);
    }

    private void broadcastUpdate(final String action,
                                 final BluetoothGattCharacteristic characteristic) {
        final Intent intent = new Intent(action);

        final byte[] data = characteristic.getValue();
        intent.putExtra(EXTRA_DATA,data);
        sendBroadcast(intent);
    }

    public void startUpTest() {
        // 血压计开机下发送指令：0xfe,0x81,0x00,0x00,0x00,0x01
        // 关机指令：0xfe,0x82,0x00,0x00,0x02,0xFF
        // 错误指令：0xfe,0x83,ResultCode,0x00,0x00,0x03
        // ResultCode=1：测量过程中没有侦测到脉搏信号。
        // ResultCode=2：测量错误，测量过长中干扰过大。
        // ResultCode=3：充气失败，充气时间过长或袖带漏气。
        // ResultCode=5：测量失败，测量的结果高压与低压相差太大。
        byte[] b = new byte[6];
        b[0] = (byte) 0xfe;// 包头
        b[1] = (byte) 0x81;
        b[2] = (byte) 0x00;
        b[3] = (byte) 0x00;
        b[4] = (byte) 0x00;
        b[5] = (byte) 0x01;
        try {
            BluetoothGattService mBluetoothGattService = mBluetoothGatt.getService(UUID.fromString(SampleGattAttributes.BloodPressure.SERVICE_UUID));
            BluetoothGattCharacteristic mBluetoothGattServiceCharacteristic = mBluetoothGattService.getCharacteristic(UUID.fromString(SampleGattAttributes.BloodPressure.WRITE_UUID));
            mBluetoothGattServiceCharacteristic.setValue(b);
            this.writeCharacteristic(mBluetoothGattServiceCharacteristic);
        } catch (Exception e) {
            isConnected = false;
            LogUtil.exception(e);
        }
    }

    /**
     * 给汗液机发送read命令
     */
    public void startSweatUpTest() {
        /**
         *开始位: (2位)NF
         *设备码: (3位)SMA
         厂商码:  (3位)N&F
         设备编号:(12位)通配地址
         当前时间  (8位) {年月日时分秒}
         帧标志    (1位) 0 代表最后一帧 1 代表还有下一帧
         命令:    4位 READ
         数据长度: 4位
         数据:    未知
         CRC校验和:1位
         结束位:   1位
         *
         */
        //需要发送给汗液分析仪的数据 开始位=8  设备uuid=12  命令=4 数据长度=2  效验=1  接受=1
        byte byteUUID[] = new byte[39];

        //1，开始位NF： 8个字节
        byte byteStart[] = {0x4e, 0x46, 0x53, 0x4d, 0x41, 0x4e, 0x26, 0x46};
        for (int i = 0; i < 8; i++) {
            byteUUID[i] = byteStart[i];
        }

        //2，设备的uuid  12字节
        byte uuidbytes[] = {(byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa,
                (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa};
        for (int i = 0; i < 12; i++) {//12字节
            byteUUID[i + 8] = uuidbytes[i];
        }

        //3,当前时间 8字节
        byte nowTime[] = new byte[8];
        nowTime[0] = 0x7b;//{
        nowTime[7] = 0x7d;//}
        for (int i = 0; i < 6; i++) {
            nowTime[i + 1] = Byte.valueOf(DateUtil.date2String(new Date().getTime(), "yyMMddHHmmss").substring(i * 2, i * 2 + 2));
        }
        for (int i = 0; i < 8; i++) {
            byteUUID[i + 20] = nowTime[i];
        }

        //4,标志位 1字节
        byteUUID[28] = 0x00;

        //5，读数据READ 4字节
        byte readbyte[] = {0x52, 0x45, 0x41, 0x44};

        for (int i = 0; i < 4; i++) {//8字节
            byteUUID[i + 29] = readbyte[i];
        }

        //6 数据长度 4字节
        byte dataLength[] = {0x00, 0x00, 0x00, 0x00};
        for (int i = 0; i < 4; i++) {
            byteUUID[i + 33] = dataLength[i];
        }

        //7，校验位 1位
        byte crcCheck = CRC8.calcCrc8(Arrays.copyOf(byteUUID,byteUUID.length-2));
        byteUUID[37] = crcCheck;
        //8结束位 1位
        byteUUID[38] = 0x2e;

        try {
            BluetoothGattService mBluetoothGattService = mBluetoothGatt.getService(UUID.fromString(SampleGattAttributes.Sweat.SERVICE_UUID));
            BluetoothGattCharacteristic mBluetoothGattServiceCharacteristic = mBluetoothGattService.getCharacteristic(UUID.fromString(SampleGattAttributes.Sweat.WRITE_UUID));
            mBluetoothGattServiceCharacteristic.setValue(byteUUID);
            this.writeCharacteristic(mBluetoothGattServiceCharacteristic);
//            LogUtil.e("发送读取命令", ProtocolParser.binaryToHexString(byteUUID) + ">>>>" + byteUUID.length);
        } catch (Exception e) {
            isConnected = false;
            LogUtil.exception(e);
        }
    }

    public class LocalBinder extends Binder {
        public BluetoothLeService getService() {
            return BluetoothLeService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        type = intent.getStringExtra("type");
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // After using a given device, you should make sure that BluetoothGatt.close() is called
        // such that resources are cleaned up properly.  In this particular example, close() is
        // invoked when the UI is disconnected from the Service.
        close();
        return super.onUnbind(intent);
    }

    private final IBinder mBinder = new LocalBinder();

    /**
     * Initializes a reference to the local Bluetooth adapter.
     *
     * @return Return true if the initialization is successful.
     */
    public boolean initialize() {
        // For API level 18 and above, get a reference to BluetoothAdapter through
        // BluetoothManager.
        if (mBluetoothManager == null) {
            mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                Log.e(TAG, "Unable to initialize BluetoothManager.");
                return false;
            }
        }

        mBluetoothAdapter = mBluetoothManager.getAdapter();
        if (mBluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
            return false;
        }
        return true;
    }

    /**
     * Connects to the GATT server hosted on the Bluetooth LE device.
     *
     * @param address The device address of the destination device.
     *
     * @return Return true if the connection is initiated successfully. The connection result
     *         is reported asynchronously through the
     *         {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     *         callback.
     */
    public boolean connect(final String address) {
        if (mBluetoothAdapter == null || address == null) {
            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }

        // Previously connected device.  Try to reconnect.
        if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
                && mBluetoothGatt != null) {
            Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
            return mBluetoothGatt.connect();
        }

        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        if (device == null) {
            Log.w(TAG, "Device not found.  Unable to connect.");
            return false;
        }
        // We want to directly connect to the device, so we are setting the autoConnect
        // parameter to false.
        mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
        Log.d(TAG, "Trying to create a new connection.");
        mBluetoothDeviceAddress = address;
        return true;
    }

    /**
     * Disconnects an existing connection or cancel a pending connection. The disconnection result
     * is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void disconnect() {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.disconnect();
    }

    /**
     * After using a given BLE device, the app must call this method to ensure resources are
     * released properly.
     */
    public void close() {
        if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.close();
        mBluetoothGatt = null;
    }

    /**
     * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
     * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
     * callback.
     *
     * @param characteristic The characteristic to read from.
     */
    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.readCharacteristic(characteristic);
    }

    public void writeCharacteristic(BluetoothGattCharacteristic characteristic){
        mBluetoothGatt.writeCharacteristic(characteristic);
    }

    /**
     * Enables or disables notification on a give characteristic.
     *
     * @param characteristic Characteristic to act on.
     * @param enabled If true, enable notification.  False otherwise.
     */
    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
                                              boolean enabled) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

        // This is specific to Heart Rate Measurement.
//        if (UUID_BLOOD_SUGAR.equals(characteristic.getUuid())) {
//            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
//                    UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
//            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
//            mBluetoothGatt.writeDescriptor(descriptor);
//        }
        BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
        if (enabled) {
            clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        } else {
            clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
        }
        mBluetoothGatt.writeDescriptor(clientConfig);
    }

    /**
     * Retrieves a list of supported GATT services on the connected device. This should be
     * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
     *
     * @return A {@code List} of supported services.
     */
    public List<BluetoothGattService> getSupportedGattServices() {
        if (mBluetoothGatt == null) return null;

        return mBluetoothGatt.getServices();
    }

    public static IntentFilter makeGattUpdateIntentFilter() {
        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
        intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
        intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
        intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
        return intentFilter;
    }
}
